package com.panopset.compat;

import java.io.StringWriter;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Random;

/**
 * Math utilities.
 *
 * @author Karl Dinwiddie
 *
 */
public final class MathUtil {

    /**
     * Random instance.
     */
    private Random random;

    /**
     * @return Random instance.
     */
    private Random getRandom() {
        if (random == null) {
            random = new Random();
        }
        return random;
    }

    /**
     * Return a random integer. Underlying java.util.Random instance is kept in
     * a private static singleton. min can be greater than max, the code will
     * simply swap them.
     *
     * <h5>References</h5>
     * <ul>
     * <li>
     * <a href="http://www.cs.geneseo.edu/~baldwin/reference/random.html">
     * http://www.cs.geneseo.edu/~baldwin/reference/random.html</a></li>
     * </ul>
     *
     * @param min
     *            Minimum.
     * @param max
     *            Maximum.
     * @return Random int between min and max values.
     */
    public static int random(final int min, final int max) {
        if (min == max) {
            return min;
        }
        int shift = min;
        if (min > max) {
            shift = max;
        }
        return MathUtil.getInstance().getRandom().nextInt(
                Math.abs(max - min) + 1)
                + shift;
    }

    /**
     *
     * @param s String to parse.
     * @return int value of s, or -1 if it can't be parsed to an int.
     */
    public static int getIntValue(final String s) {
        if (s == null) {
            return -1;
        }
        if (s.indexOf(".") > -1) {
            try {
                BigDecimal bd = new BigDecimal(s);
                return bd.intValue();
            } catch (NumberFormatException ex) {
                return -1;
            }
        }
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

    /**
     *
     * @param s String to parse.
     * @return long value of s, or -1 if it can't be parsed to an int.
     */
    public static long getLongValue(final String s) {
        if (s == null) {
            return -1;
        }
        if (s.indexOf(".") > -1) {
            try {
                BigDecimal bd = new BigDecimal(s);
                return bd.longValue();
            } catch (NumberFormatException ex) {
                return -1;
            }
        }
        try {
            return Long.parseLong(s);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

    /**
     * Based on SingletonHolder inner class by Bill Pugh.
     *
     *
     * <h5>References</h5>
     * <ul>
     * <li>
     * <a href="http://en.wikipedia.org/wiki/Singleton_pattern">
     * http://en.wikipedia.org/wiki/Singleton_pattern </a></li>
     * </ul>
     *
     */
    private static final class Singleton {
        /**
         * Instance variable.
         */
        private static final MathUtil INSTANCE = new MathUtil();

        /**
         * Private constructor.
         */
        private Singleton() {
            // Prevent instantiation.
        }
    }

    /**
     * @return static MathUtil instance.
     */
    private static MathUtil getInstance() {
        return Singleton.INSTANCE;
    }

    /**
     * Private singleton constructor.
     */
    private MathUtil() {
        // Prevent outside instantiation.
    }

    /**
     * Get a percentage.
     *
     * @param amount
     *            Amount to get percentage of.
     * @param total
     *            Full amount possible.
     * @param scale
     *            How many decimal places to return. -1 means no limit.
     * @return Percentage as String, ie &quot;<b>10.23</b>&quot; percent.
     */
    public static Percentage percent(final long amount, final long total,
            final int scale) {
        return percent(new BigDecimal(amount), new BigDecimal(total), scale);
    }

    /**
     * Get a percentage.
     *
     * @param amount
     *            Amount to get percentage of.
     * @param total
     *            Full amount possible.
     * @param scale
     *            How many decimal places to return. -1 means no limit.
     * @return Percentage as String, ie &quot;<b>10.23</b>&quot; percent.
     */
    public static Percentage percent(final BigDecimal amount,
            final BigDecimal total, final int scale) {

        // Divide by 0, show as undefined.
        if (total.compareTo(BigDecimal.ZERO) == 0) {
            return new Percentage("------", null);
        }

        BigDecimal bd = amount.multiply(new BigDecimal(ONEHUNDRED)).divide(
                total, scale, BigDecimal.ROUND_HALF_UP);
        if (scale == -1) {
            return new Percentage("" + bd, bd);
        }
        StringWriter sw = new StringWriter();
        sw.append("0.");
        for (int i = 0; i < scale; i++) {
            sw.append("0");
        }
        return new Percentage(new DecimalFormat(sw.toString()).format(bd),
                bd);
    }

    /**
     *
     * @param s
     *            String to parse.
     * @return BigDecimal result. 0 if it can not be parsed.
     */
    public static BigDecimal parse(final String s) {
        BigDecimal rtn = BigDecimal.ZERO;
        if (s == null) {
            return rtn;
        }
        try {
            rtn = new BigDecimal(s);
        } catch (NumberFormatException ex) {
            return rtn;
        }
        return rtn;
    }

    /**
     *
     * @param s
     *            String to parse.
     * @param scale
     *            scale.
     * @return BigDecimal result. 0 if it can not be parsed.
     */
    public static BigDecimal parse(final String s, final int scale) {
        BigDecimal rtn = parse(s);
        return rtn.setScale(scale);
    }

    /**
     * Percentage representation and double value.
     */
    public static final class Percentage {

        /**
         * String representation of value.
         */
        private final String string;

        /**
         * Percentage represented as a double.
         */
        private BigDecimal pvalue;

        /**
         * @return Percentage as a double.
         */
        public BigDecimal getValue() {
            return pvalue;
        }

        /**
         * @param stringValue
         *            String representation of the percentage.
         * @param value
         *            Double value of the percentage.
         */
        private Percentage(final String stringValue, final BigDecimal value) {
            string = stringValue;
            if (value == null) {
                pvalue = BigDecimal.ZERO;
            } else {
                pvalue = value;
            }
        }

        @Override
        public String toString() {
            return string;
        }

    }

    /**
     * 2.
     */
    public static final int TWO = 2;

    /**
     * 3.
     */
    public static final int THREE = 3;

    /**
     * 4.
     */
    public static final int FOUR = 4;

    /**
     * 5.
     */
    public static final int FIVE = 5;

    /**
     * 6.
     */
    public static final int SIX = 6;

    /**
     * 7.
     */
    public static final int SEVEN = 7;

    /**
     * 8.
     */
    public static final int EIGHT = 8;

    /**
     * 9.
     */
    public static final int NINE = 9;

    /**
     * 10.
     */
    public static final int TEN = 10;

    /**
     * Hex radix is 16.
     */
    public static final int HEX_RADIX = 16;

    /**
     * 24.
     */
    public static final int TWENTYFOUR = 24;

    /**
     * 60.
     */
    public static final int SIXTY = 60;

    /**
     * 100.
     */
    public static final int ONEHUNDRED = 100;

    /**
     * 1000.
     */
    public static final int ONETHOUSAND = 1000;

    /**
     * 10000.
     */
    public static final int TENTHOUSAND = 10000;

    /**
     * 100000.
     */
    public static final int HUNDREDTHOUSAND = 100000;

    /**
     * 1000000.
     */
    public static final int MILLION = 1000000;
}
